--------------------------------------------------
-- @file src/kit/core/proto.lua
-- @desc an implementation of class
-- @author doooreyn
-- @date 2020.11.14
-- @depends tolua
-- @more 提供类的一种实现，以及相关的辅助方法
--------------------------------------------------
local checker = require('src.kit.core.checker')
local type = type
local error = error
local tolua = tolua
local ipairs = ipairs
local assert = assert
local strfmt = string.format
local setmetatable = setmetatable
local getmetatable = getmetatable
local tip_1 = 'class "%s" with invalid super class type "%s"'
local tip_2 = 'class "%s" with more than one creating function'
local tip_3 = tip_2 .. ' or native class'
local tip_4 = 'class "%s" with invalid super type'

--
-- @method setmetatableindex
-- @desc apply __index to table's metatable
-- @param t table
-- @param index table/function
--
local function setmetatableindex(t, index)
    if type(t) == 'userdata' then
        local peer = tolua.getpeer(t)
        if not peer then
            peer = {}
            tolua.setpeer(t, peer)
        end
        setmetatableindex(peer, index)
    else
        local mt = getmetatable(t)
        if not mt then
            mt = {}
        end
        if not mt.__index then
            mt.__index = index
            setmetatable(t, mt)
        elseif mt.__index ~= index then
            setmetatableindex(mt, index)
        end
    end
end

--
-- @method class
-- @desc implementation of class
-- @param classname string class's name
-- @param ... table(class)/function/nil supers
-- @return table(class)
--
local function class(classname, ...)
    local cls = {__cname = classname, isclass = 0x01}

    local supers = {...}
    for _, super in ipairs(supers) do
        local super_type = type(super)
        local ok = checker.some(super_type, 'table', 'function')
        assert(ok, strfmt(tip_1, classname, super_type))

        if checker.is_function(super) then
            assert(checker.is_nil(cls.__create), strfmt(tip_2, classname))
            cls.__create = super
        elseif checker.is_table(super) then
            if super['.isclass'] then
                assert(checker.is_nil(cls.__create), strfmt(tip_3, classname))
                cls.__create = function()
                    return super:create()
                end
            else
                cls.__supers = cls.__supers or {}
                cls.__supers[#cls.__supers + 1] = super
                if not cls.super then
                    cls.super = super
                end
            end
        else
            error(strfmt(tip_4, classname), 0)
        end
    end

    cls.__index = cls
    if not cls.__supers or #cls.__supers == 1 then
        setmetatable(cls, {__index = cls.super})
    else
        setmetatable(
            cls,
            {
                __index = function(_, key)
                    local __supers = cls.__supers
                    for i = 1, #__supers do
                        local super = __supers[i]
                        if super[key] then
                            return super[key]
                        end
                    end
                end
            }
        )
    end

    if not cls.ctor then
        cls.ctor = function()
        end
    end

    cls.new = function(...)
        local instance
        if cls.__create then
            instance = cls.__create(...)
        else
            instance = {}
        end
        setmetatableindex(instance, cls)
        instance.class = cls
        instance:ctor(...)
        return instance
    end

    cls.create = function(_, ...)
        return cls.new(...)
    end

    cls.__inst__ = nil

    cls.instance = function(_, ...)
        if not cls.__inst__ then
            cls.__inst__ = cls.new(...)
        end
        return cls.__inst__
    end

    cls.release = function(_)
        cls.__inst__ = nil
    end

    return cls
end

--
-- @method chains
-- @desc wrap all supers' in chains
-- @param cls table(class)
--
local function chains(cls)
    local src = cls.super
    local list = {cls}
    while checker.is_class(src) do
        table.insert(list, src)
        src = src.super
    end
    return list
end

--
-- exports
--
return {class = class, chains = chains, setmetatableindex = setmetatableindex}
